// locutor command line client
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Locutor.scala 26 2009-11-10 14:57:05Z normen.mueller $
package locutor.cli

import java.io.File

import scaup.Views._
import scaup.util.Timer

/** 
 * A document model ℳ = <Ḡ, ℰ, ℛ> is a triple constituted of
 *   o a dependency graph model Ḡ = <I, D>.
 *       Ḡ is a directed, classified, labeled multi-graph, where
 *       I is the set of INFOMs and
 *       D ⊆ I X I is the set of dependencies both classified.
 *   o a set of equiv-relations ℰ on Ḡ(I) (Note: declarative syntax not yet implemened)
 *   o a rule system ℛ = <ℙ, ℂ> on Ḡ
 *     ℙ denotes a set of propagation rules on Ḡ and
 *     ℂ a set of consistency rules for Ḡ.
 * 
 * A dependency graph instance d: Ḡ
 * 
 * Change Impact Analysis (CIA):
 * 
 * For a rule system R being applicable for rippling (↩) differrences δ into a dependency graph D, 
 * the graph model G(R) has to be a subset of the graph model G(D), short G(R) ⊑ G(D).
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
object Locutor {
  // timing
  private val diffTimer: Timer = new Timer
  private var dgTimer: Timer = new Timer
  private var rsTimer: Timer = new Timer
  private var rippleTimer: Timer = new Timer
  
  def main(args: Array[String]) {
    // ===================================================================================================== CONF
    val depFile   = args(0).toFile
    val equivFile = args(1).toFile
    val ruleFile  = args(2).toFile
    val oldFile   = args(3).toFile
    val newFile   = args(4).toFile    
    println("=============================================================================================== DIFF")
    //@future declarative syntax for equivalence systems
    //val E = loadEquivalenceSystem(equivFile)
    val Δ = for(δ <- diff(oldFile, newFile) withEquivs locutor.cli.eq.SVNBookEquivs) yield δ match { case cmd =>
      println(new StringBuilder append "# " append cmd.prefix append " at " append cmd.path)
      δ
    }
    println("=============================================================================================== KB(CIA)")
    val Ｄ = loadDependencyGraph(depFile)      
    val ℛ = loadRuleSystem(ruleFile)
    println("=============================================================================================== RIP(CIA)")    
    print("Rippling...")
    rippleTimer.start
                                              val ℒ = Ｄ ↩ Δ having ℛ
    rippleTimer.stop
    println(ident(33, "Done ("+rippleTimer+")."))
    //println("Done ("+((dgTimer.elapsed+rsTimer.elapsed+rippleTimer.elapsed) / 1000.0)+" sec).\n")
    // flushing long-range effects
    ℒ foreach (ℓ => flush(ℓ))    
    println("=============================================================================================== MARK(RIP)")
    mark(newFile, ℒ)    
  } 
  
  private def ident(offset: Int, string: String) = (new StringBuilder /: (1 to offset))((b, i) => b append " ") append string

  private def diff(oldFile: File, newFile: File) = new {
    def withEquivs(equivs: scaup.eq.EquivalenceSystem[scala.xml.Node]) = {
      print("Differencing...")
      diffTimer.start
      val delta = scalaxx.diff.Differ.diff(equivs, oldFile, newFile)
      diffTimer.stop
      println(ident(30, "Done ("+diffTimer+")."))
      delta
    }
  }
  
  private def loadDependencyGraph(description: File) = {
    print("Loading dependency graph...")
    dgTimer.start    
    val dg = locutor.model.DependencyGraph.parse(description)    
    dgTimer.stop
    println(ident(18, "Done ("+dgTimer+")."))       
    dg
  }
  
  private def loadRuleSystem(description: File) = {
    print("Loading rule system...")
    rsTimer.start    
    val rs = locutor.model.RuleSystem.parse(description)    
    rsTimer.stop
    println(ident(23, "Done ("+rsTimer+")."))      
    rs
  }

  private def flush(l: (scalaxx.diff.Command, locutor.cia.ImpactGraph)) = l match {
    case (cmd, impact) =>
      val buf = new StringBuilder append "# " append cmd.prefix append " at " append cmd.path append " has implicit effects on:\n"
      (buf /: (impact.V filter (_.label("effect") == "implicit")))((b, node) => {
        b append "-> " append node.id append ":" append node.label("type") append "\n      "
        b append "in " append node.url append "\n      "
        b append "at " append node.path append "\n" 
      })   
      println(buf)
  } 
  
  private def mark(file: File, L: Map[scalaxx.diff.Command, locutor.cia.ImpactGraph]) = {
    import scalaxx.path._
    import scala.xml.{Elem, Text, PrefixedAttribute, Null}    
    val markers = Map(Ʈ↘(1) -> scalaxx.util.Mark(None, Some((Some(new PrefixedAttribute("xmlns", "locutor", Text("nrm"), Null)), None)), None)) ++
      (L flatMap (l => l match { case (cmd, impact) => impact.V filter (_.label("effect") == "implicit") map (v => (v.path, scalaxx.util.Mark(
        /* before */ None,
        /* self   */ Some(None, Some(<locutor:affected from={cmd.path.toString} by={v.label("ct")}></locutor:affected>)),
        /* after  */ None)))
    }))    
    val elem = scalaxx.util.Marker.mark(astOf(file), markers).elements.next
    val markedFile = file.parentPath + "/" + file.name + ".affected." + file.ext.getOrElse("xml")
    
    val doc = """<?xml version="1.0"?><!DOCTYPE book SYSTEM "/Users/nmueller/mydoc/svnbook/original/src/tools/dtd/docbook-4.4/docbookx.dtd"><?xml-stylesheet type="text/css" href="styles.css"?><?xml-stylesheet type="text/xsl" href="../original/src/tools/html-stylesheet.xsl"?>""" + scala.xml.Utility.toXML(elem) >>> markedFile.toFile
    println("Marked document: " + markedFile)
  }
  
  private def astOf(file: File) = scala.xml.XML.loadFile(file)
  
}
